/*
 * LegoBot v1.0-a
 * http://code.google.com/p/legocode/wiki/LegoBot
 * Copyright (c) 2009 MindstormsKid <mindstormskd@gmail.com>
 * Licensed under the MIT license <http://www.opensource.org/licenses/mit-license.php>
 *
 * IRC Uno module <http://legocode.googlecode.com/svn/trunk/legobot/content/uno.js>
 */

// Starts a new uno game. Also contains the list of current uno games.
// Usage: var game = new IRC.Uno(IRCE of starter);
IRC.Uno = function(e){
	this.merge({
		chan: e.chan,
		starterhost: e.host,
		cards: [
			"W", "W", "W", "W", "WD4", "WD4", "WD4", "WD4",
			"RR", "RR", "BR", "BR", "GR", "GR", "BR", "BR",
			"RS", "RS", "BS", "BS", "GS", "GS", "BS", "BS",
			"RD2", "RD2", "BD2", "BD2", "GD2", "GD2", "BD2", "BD2",
			"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9",
			"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9",
			"G0", "G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9", "G1", "G2", "G3", "G4", "G5", "G6", "G7", "G8", "G9",
			"Y0", "Y1", "Y2", "Y3", "Y4", "Y5", "Y6", "Y7", "Y8", "Y9", "Y1", "Y2", "Y3", "Y4", "Y5", "Y6", "Y7", "Y8", "Y9"
		],
		started: false,
		usedCards: [],
		players: {},
		turns: [],
		turn: 0,
		reversed: false,
		player: null
	});
	
	IRC.say('Starting a round of uno! Type "!uno join" to join, "!uno start" to deal, or "!uno help" for help.', this.chan);
	this.join(e);
};
IRC.Uno.games = {};

IRC.Uno.prototype.merge({
	/********* events *********/
	// Joins a user to a game
	join: function(e){
		if (this.players[e.host]) {
			IRC.notice("You are already in the " + this.chan + " uno game.", e.nick);
			return;
		}
		
		this.players[e.host] = {
			nick: e.nick,
			cards: [],
			draw: true
		};
		for (var i = 0; i < 7; i++) { this.players[e.host].cards[i] = this.spliceCard(false, true); }
		IRC.say(e.nick + " has joined the game! " + this.playerstring(), this.chan);
	},
	
	// Deals a game
	deal: function(e){
		var that = this;
		this.started = true;
		while (true) {
			var card = this.spliceCard(true, true);
			if (!isNaN(card[1])) { // must be a number card
				this.usedCards[0] = card;
				break;
			}
		}
		IRC.say("Game has started!", e.chan);
		
		var i = 0;
		this.players.each(function(v, k){ that.turns[i] = k; i++; });
		
		this.player = this.players[this.turns[this.turn]];
		
		this.startTurn(e);
	},
	
	// Starts a turn. Needs to be redone...
	// Usage: game.startTurn(IRCE of the previous player, card played);
	startTurn: function(olde, card){
		var player = this.player, say = "", skippedplayer = this.players[this.turns[this.mod(this.turn - 1)]], playercards = "", that = this;
		if (olde.passed) {
			say = olde.nick + " passed! ";
		} else if (this.usedCards[0][1] == "S") {
			say = skippedplayer.nick + " was skipped! ";
		} else if (this.usedCards[0][1] == "R") {
			say = olde.nick + " reversed it! ";
		} else if (this.usedCards[0][1] == "D") {
			say = skippedplayer.nick + " drew two cards and was skipped! ";
		} else if (card && card == "WD4") {
			say = skippedplayer.nick + " drew four cards and was skipped! ";
		}
		IRC.say(say + player.nick + "'s turn. Top card is " + this.colorCard(this.usedCards[0]) + ".", this.chan);
		
		player.cards.each(function(v){ playercards += " " + that.colorCard(v); });
		IRC.notice("Your turn in " + this.chan + "! Your cards are:" + playercards + ".", player.nick);
		player.draw = true;
	},
	
	// Checks to see if the user has uno or has won... Intergrate this into something else?
	unoout: function(e){
		if (this.players[e.host].cards.length === 1) {
			IRC.say(e.nick + " has UNO!", e.chan);
		} else if (this.players[e.host].cards.length === 0) {
			IRC.say(e.nick + " won!! Good job, everyone!", e.chan);
			delete IRC.Uno.games[e.chan];
			return false;
		}
		return true;
	},
	
	// Plays a card. Needs cleanup :(
	// Usage: game.play(IRCE of player, cardi, card, [wildcard color]);
	play: function(e, cardi, card, color){
		if (!color) {
			if (!(card[0] == this.usedCards[0][0] || card[1] == this.usedCards[0][1])) {
				IRC.notice("That card is not the same color or number/letter as the top card in the " + e.chan + " uno game.", e.nick); return;
			}
			
			this.usedCards.unshift(this.players[e.host].cards.splice(cardi, 1)[0]);
			if (!this.unoout(e)) { return; }
			if (card[1] == "R") {
				this.reversed = this.reversed ? false : true;
				if (this.turns.length == 2) { this.nextTurn(); } // in two player mode reverse should act like skip 
			} else if (card[1] == "S") {
				this.nextTurn();
			} else if (card[1] == "D") {
				this.nextTurn();
				var card1 = this.spliceCard(false, true), card2 = this.spliceCard(false, true);
				this.player.cards.push(card1, card2);
				IRC.notice("You drew: " + this.colorCard(card1) + " " + this.colorCard(card2) + ".", this.player.nick);
			}
		} else {
			if (card == "W") {
				this.players[e.host].cards.splice(cardi, 1);
				this.usedCards.unshift(color + "*");
				if (!this.unoout(e)) { return; }
			} else if (card == "WD4") {
				if (this.players[e.host].cards.some(function(v, i){ return this.usedCards[0][0] == this.players[e.host].cards[i][0]; }, this)) {
					IRC.notice("You can't play a Wild Draw Four if you have a card that is the same color as the top card.", e.nick);
					return;
				}
				
				this.players[e.host].cards.splice(cardi, 1);
				this.usedCards.unshift(color + "*");
				if (!this.unoout(e)) { return; }
				this.nextTurn();
				var cards = [this.spliceCard(false, true), this.spliceCard(false, true), this.spliceCard(false, true), this.spliceCard(false, true)];
				this.player.cards.push(cards[0], cards[1], cards[2], cards[3]);
				IRC.notice("You drew: " + this.colorCard(cards[0]) + " " + this.colorCard(cards[1]) + " " + this.colorCard(cards[2]) + " " + this.colorCard(cards[3]) + ".",
					this.player.nick);
			}
		}
		
		this.nextTurn();
		this.startTurn(e, card);
	},
	
	// Removes a player from the game
	leave: function(e){
		var that = this;
		this.players[e.host].cards.each(function(v){ that.usedCards.push(v); });
		delete this.players[e.host];
		
		this.turns.splice(this.turns.indexOf(e.host), 1);
		if (this.turns.length === 0) {
			IRC.say("The last player has left the game! Game over.", e.chan);
			delete IRC.Uno.games[e.chan];
		} else {
			IRC.say(e.nick + " has left the game. " + this.playerstring(), e.chan);
			
			if (this.player.nick == e.nick) { this.nextTurn(); this.startTurn(e); } // this player's turn?
		}
	},
	
	// Pushes the game into the next turn.
	nextTurn: function(){
		this.reversed ? this.turn-- : this.turn++;
		this.turn = this.mod(this.turn);
		this.player = this.players[this.turns[this.turn]];
	},
	/********* /events *********/
	
	/********* util *********/
	// Modular math, uno edition.
	mod: function(n){
		var m = this.turns.length - 1;
		return n < 0 ? m : (n > m ? 0 : n);
	},
	
	// Colors a card with IRC colors.
	colorCard: function(card){
		return "\u0002\u0003" + ({ R: 4, G: 9, B: 12, Y: 8, W: 0 })[card[0]] + ",1[" + card + "]\u000F";
	},
	
	// Picks a random card from the deck
	pickCard: function(returncard){
		var cardi = Math.floor(Math.random() * this.cards.length);
		return returncard ? this.cards[cardi] : cardi;
	},
	
	// Splices a random card from the deck
	// Usage: game.spliceCard(add to used cards, return card or cardi);
	spliceCard: function(addToUsed, returncard){
		if (this.cards.length === 0) { this.cards = this.usedCards.splice(1, Infinity); } // deal
		var cardi = this.pickCard(), card = this.cards.splice(cardi, 1)[0];
		if (addToUsed) { this.usedCards.unshift(card); }
		return returncard ? card : cardi;
	},
	
	// Returns the current players
	playerstring: function(){
		var p = [];
		this.players.each(function(v){ p.push(v.nick); });
		return "Current players are: " + p.join(", ") + ".";
	}
	/********* /util *********/
});


// Adds the command
IRC.addCmd("uno", 0, true, function(e, command){
	var game = IRC.Uno.games[e.chan] || null;
	command = (command || "start").toLowerCase();
	
	switch (command) {
		case "start":
			if (!game) {
				IRC.Uno.games[e.chan] = new IRC.Uno(e);
			} else if (game.started === false) {
				if (game.starterhost == e.host) {
					game.deal(e);
				} else {
					IRC.notice("Sorry, only the game starter can deal an uno game.", e.nick);
				}
			} else if (game.started === true) {
				IRC.notice("It seems that there is already an uno game running in " + e.chan + ".", e.nick);
			}
			break;
		case "help":
			IRC.notice("Uno commands: start; help; join; myinfo; leave; nick; pass; draw; play;", e.nick);
			IRC.notice("Use uno commands with !uno <command>. Use help <command> for more info on that command", e.nick);
			break;
		default:
			if (game === null) { IRC.notice('There is no uno game in ' + e.chan + '. Type "!uno start" to start one.', e.nick); return; }
			
			switch (command) {
				case "join":
					if (!game.started) { game.join(e); } else { IRC.notice('Sorry, an uno game is currently being played in ' + e.chan + '.', e.nick); }
					break;
				default:
					if (!game.players[e.host]) { IRC.notice("You aren't playing uno in " + e.chan + ".", e.nick); return; }
					
					switch (command) {
						case "myinfo":
							if (!game.started) { IRC.notice("The uno game in " + e.chan + " hasn't been started yet.", e.nick); return; }
							
							break;
						case "leave":
							game.leave(e);
							break;
						case "nick":
							game.players[e.host].nick = e.nick;
							IRC.notice('"' + e.nick + '" is now the nick for "' + e.host + '".', e.nick);
							break;
						default:
							if (game.turns[game.turn] != e.host) { IRC.notice("It isn't your turn in the " + e.chan + " uno game.", e.nick); return; }
							
							switch (command) {
								case "pass":
									game.nextTurn();
									e.passed = true;
									game.startTurn(e);
									break;
								case "draw":
									if (game.players[e.host].draw) {
										var card = game.spliceCard(false, true);
										IRC.notice("You drew a " + game.colorCard(card) + " in " + e.chan + ".", e.nick);
										game.players[e.host].cards.push(card);
										game.players[e.host].draw = false;
									} else {
										IRC.notice("You have already drawn a card in " + e.chan + ".", e.nick);
									}
									break;
								default:
									if (!game.started) { IRC.notice("The uno game in " + e.chan + " has not started yet.", e.nick); return; }
									
									var play = command.match(/^^play\s([rgby][\dsrd]2?)|(?:(w|wd4)\s([rgby]))/i);
									
									if (!play) { IRC.notice("Unkown command.", e.nick); return; }
									
									var cardi = game.players[e.host].cards.indexOf((play[1] || play[2]).toUpperCase());
									
									(cardi != -1) ? game.play(e, cardi, game.players[e.host].cards[cardi], (play[3] || "").toUpperCase()) :
										IRC.notice("You don't have that card in the " + e.chan + ' uno game. Type "!uno myinfo" to see your cards.', e.nick);
							}
					}
			}
	}
});